package red.stu.pconstraint.config;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import red.stu.pconstraint.xml.XMLConfigResolver;
import red.stu.pconstraint.xml.parser.XNode;
import red.stu.pconstraint.xml.parser.XPathParser;
import red.stu.pconstraint.xml.parser.exception.BuilderException;

import java.io.InputStream;
import java.io.Reader;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;

/**
 * @author yihrmc @stu.red
 * @date 2019/11/14 9:55
 */
public class XMLConfigBuilder extends BaseBuilder<Configuration> {

    private boolean parsed;
    private XPathParser parser;
    private String environment;

    public XMLConfigBuilder(@NotNull Reader reader, boolean validation) {
        this(reader, null, new Properties(), validation);
    }

    public XMLConfigBuilder(@NotNull Reader reader, @Nullable String environment, boolean validation) {
        this(reader, environment, new Properties(), validation);
    }

    public XMLConfigBuilder(@NotNull Reader reader, @Nullable String environment, @NotNull Properties props, boolean validation) {
        this(new XPathParser(reader, validation, props, new XMLConfigResolver()), environment, props);
    }

    public XMLConfigBuilder(@NotNull InputStream inputStream, boolean validation) {
        this(inputStream, null, new Properties(), validation);
    }

    public XMLConfigBuilder(@NotNull InputStream inputStream, @Nullable String environment, boolean validation) {
        this(inputStream, environment, new Properties(), validation);
    }

    public XMLConfigBuilder(@NotNull InputStream inputStream, @Nullable String environment, @NotNull Properties props, boolean validation) {
        this(new XPathParser(inputStream, validation, props, new XMLConfigResolver()), environment, props);
    }

    public XMLConfigBuilder(@NotNull XPathParser parser, @Nullable String environment, @NotNull Properties props) {
        super(new Configuration());
        this.parser = parser;
        this.environment = environment;
        entity.setVariables(props);
    }

    public Configuration parse() {
        if (parsed) {
            throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        }
        parsed = true;
        parseConfiguration(parser.evalNode("/configuration"));
        return entity;
    }

    private void parseConfiguration(XNode root) {
        try {
            variablesElement(root.evalNode("variables"));
            scannerElement(root.evalNode("scanner"));
            mappersElement(root.evalNode("mapper"));
//            //issue #117 read properties first
//            propertiesElement(root.evalNode("properties"));
//            Properties settings = settingsAsProperties(root.evalNode("settings"));
//            loadCustomVfs(settings);
//            typeAliasesElement(root.evalNode("typeAliases"));
//            pluginElement(root.evalNode("plugins"));
//            objectFactoryElement(root.evalNode("objectFactory"));
//            objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
//            reflectorFactoryElement(root.evalNode("reflectorFactory"));
//            settingsElement(settings);
//            // read it after objectFactory and objectWrapperFactory issue #631
//            environmentsElement(root.evalNode("environments"));
//            databaseIdProviderElement(root.evalNode("databaseIdProvider"));
//            typeHandlerElement(root.evalNode("typeHandlers"));
//            mapperElement(root.evalNode("mappers"));
        } catch (Exception e) {
            throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
        }
    }

    private void variablesElement(XNode context) {
        if (context != null) {
            context.forEachChildren((child) -> {
                String name = child.getStringAttribute("name");
                String value = child.getStringAttribute("value");
                if (name != null && value != null) {
                    entity.getVariables().put(name, value);
                }
            });
        }
    }

    private void scannerElement(XNode context) {
        if (context != null) {
            List<XNode> childrens = context.getChildren();
            if (childrens.size() == 0) {
                return;
            }
            List<Configuration.Scanner> scannerList = entity.getScannerList();
            if (scannerList == null) {
                scannerList = new LinkedList<>();
                entity.setScannerList(scannerList);
            }
            for (XNode child : childrens) {
                String name = child.getName();
                String value = child.getStringAttribute("value");
//                boolean openProcessControl = child.getBooleanAttribute("ProcessControl", true);
//                boolean openProcessNode = child.getBooleanAttribute("ProcessNode", true);
//                boolean openProcessPersistence = child.getBooleanAttribute("ProcessPersistence", true);
                scannerList.add(new Configuration.Scanner()
                        .setType(name)
                        .setValue(value)
//                        .setOpenProcessControl(openProcessControl)
//                        .setOpenProcessNode(openProcessNode)
//                        .setOpenProcessPersistence(openProcessPersistence)
                );
            }
        }
    }

    private void mappersElement(XNode context) throws Exception {
        if (context != null) {
            List<XNode> childrens = context.getChildren();
            if (childrens.size() == 0) {
                return;
            }
            List<Configuration.Mapper> mapperList = entity.getMapperList();
            if (mapperList == null) {
                mapperList = new LinkedList<>();
                entity.setMapperList(mapperList);
            }
            for (XNode child : childrens) {
                String name = child.getName();
                String value = child.getStringAttribute("value");
                if (value == null) {
                    value = child.getStringBody();
                }
                mapperList.add(new Configuration.Mapper()
                        .setType(name)
                        .setValue(value));
            }
        }
    }

}
